Error Handling(Assertion & Exception)

C++에서는 단정(Assertion)과 예외(Exception)을 통해서 예기치 않은 동작을 처리한다.
단정은 오류를 감지하기 위한 방법이고,
예외는 프로그램의 적절한 지속을 방해하는 예외적인 상황을 위한 방법이다.
단정(Assertion)
<cassert>의 매크로 assert는 C에서 상속받은 매크로로 표현식을 계산한 뒤, 결과가 false이면
프로그램을 즉시 종료한다.

assert는 프로그래밍 오류를 감지하는데 사용된다.
#include <cassert>
double square_root(double x){
check_somehow(x>=0);
...
assert(result>=0.0);
return result;
}
assert 결과가 false라면 프로그램은 아래의 오류를 출력한다.

assert_test: assert_test.cpp:10: double square_root(double):
Assertion ‘result >= 0.0’ failed.

assert는 간단한 매크로 선언만으로 오류를 완전히 사라지게 할 수 있다
<cassert> include 하기 전에 NDEBUG를 정의할 수 있다.

<assert.h>
#define NDEBUG
#include <cassert>
NDEBUG를 정의함으로써 모든 단정을 비활성화 할 수 있다.
릴리즈 할 때, 해당 선언을 해주면, 이후 모든 assert는 비활성화 됨
위 처럼 소스를 변경하지 않고, gcc에서 플래그(리눅스에서 -D, 윈도우에서 /D)를 선언해 주어도 됨

g++ my_app.cpp -o my_app -O3 -DNDEBUG

assert 함수는 디버그 모드에서만 컴파일이 된다.
따라서, 다른 코드에 영향을 주지 않는 코드만 넣어야 함
정적 단정(static_assert)
컴파일 과정에서 감지할 수 있는 오류는 static_assert를 발생시킬 수 있다.

오류 메시지를 표시하고, 컴파일을 중지한다.
예외(Exception)
단정을 이용해서 프로그래밍 오류를 감지할 수 있지만, 실제 작업보다 훨씬 많은 작업을 해주어야 할 수도 있다.
일반적으로 작업을 수행하고, 예외를 확인인하는 것이 더 효율적일 때, 예외를 사용한다.

C에서는 전통적으로 오류 코드를 이용해서 모든 잘못될 수 있는 경우에 대해서 검사를 수행한다.
int read_matrix_file(const char* fname, ...){
fstream f(home);
if(!f.is_open()) return 1;
...
return 0
}
위 와 같은 코드는 적절한 오류 코드를 알려주지만, 해당 오류 코드에 대하여서 적절하게 대응하기 어렵다.
또한 오류 코드는 계산 결과를 반환할 수 없으며, 레퍼런스 인수로 전달되어야 한다.
(결과로 표현식을 만들 수 없음)
던지기(throw)
예외를 던지는 방법
throw를 사용하면, 계산결과를 반환하고 오류 코드를 레퍼런스 함수 인수로 전달한다.
matrix read_matrix_file(const char* fname, ...){
fstream f(fname);
if(!f.is_open()) throw "Cannot open file.";
...
}
위와 같이 오류 코드에 대한 예외 처리의 이점은 처리할 수 있는 문제만 신경 쓰면 된다.
C++를 사용하면 문자열, 숫자, 사용자 정의 타입 등 모든 타입을 예외로 처리할 수 있다.
(예외를 적절하게 처리하기 위해서, 예외 타입을 정의하거나 표준 라이브러리 메소드를 사용하는 것이 좋음)
struct cannot_open_file{}; //
void read_matrix_file(const char* fname, ...){
fstream f(fname);
if(!f.is_open()) throw cannot_open_file{};
...
}
잡기(try-catch)
예외 상황에 대처하기 위해서 예외를 붙잡아야 한다.
try{
...
} catch (e1_type& e1){
...
} catch (e2_type& e2){
...
} catch (...){ // catch(...) .
...
}
문제(에러)를 기대할 때마다 try 블록을 연다.
catch 에서 레퍼런스를 통해 예외를 잡아낸다.
예외가 발새하면 일치하는 타입을 갖는 첫 번째 catch 블록이 실행된다.
동일한 타입(하위 타입)의 추가 catch 블록은 무시됨

마지막으로 default로 catch(…)은 모든 예외를 붙잡는다.
try{
read_matrix_file("does_not_exist.dat");
} catch(cannot_open_file& e){
cerr<<"Hey guys, your file is not exist! I'm out.\n";
exit(EXIT_FAILURE); // exit <cstdlib>
}
exit(EXIT_FAILURE)는 cstdlib 헤더에 정의된 함수이다.
이후 실행이 너무 위험하거나 호출하는 함수에 예외에 대한 해결책이 없을 때만 사용
부분적인 복구 처리(try-catch & throw)
try{
read_matrix_file("does_not_exist.dat");
} catch(cannot_open_file& e){
cerr<< "Oh my hosh, the file is not there! Please caller help me.\n";
throw e;
}
호출 스택에 예외를 잡는 함수가 없기 때문에 아래와 같이 사용해도 무방함
} catch(cannot_open_file&){
...
throw;
}
빈 블록을 사용하면, 예외를 무시한다.
} catch(cannot_open_file&){} // ,
복구 처리
bool keep_trying=true;
do{
char fname[80];
cout<<"Please enter the file name: ";
cin>>fname;
try{
read_matrix_file(fname);
...
keep_trying=false;
}catch(cannot_open_file& e){
cout<<"Could not open file. Try another one!\n";
}catch(...){
cout<<"Something is fishy here. Try another file!\n";
}
}while(keep_trying);
try 구문중에 에러가 발생시, 중단하고 catch에서 해당 레퍼런스를 찾아서 구문을 시행한다.
예외의 가장 큰 장점은 오류가 발견된 상황에서 당장 처리할수 없는 문제를 나중으로 미룰 수 있다는 점이다.
전통적인 오류 처리로도 처리할 수 있지만, 예외 처리를 사용하면, 더 쉽게 구현 가능하다
C++11 부터 함수에서 예외를 던지지 않아야 함을 지정하는 지정자가 추가되었다.
double squre_root(double x) noexcept { ... }
noexcept 지정자는 square_root 호출 후에 던진 예외를 확인하지 않아도 된다는 점을 알려준다.
noexcept 지정자를 추가했는데도, 예외가 발생한 경우, 프로그램은 종료된다.

noexcept는 컴파일 타입 조건에 의존할 수 있다